package DDG::Goodie::Loan;
# ABSTRACT: Calculate monthly payment and total interest payment for a conventional mortgage loan

use strict;
use DDG::Goodie;
use Locale::Currency::Format;

triggers any => 'loan', 'mortgage', 'borrow';

zci answer_type => 'loan';
zci is_cached   => 1;

# Monthly payment calculation from http://en.wikipedia.org/wiki/Mortgage_calculator
sub loan_monthly_payment {
    my ($p, $r, $n) = @_;
    return ($r / (1 - (1 + $r)**(-1 * $n))) * $p;
}

# A map of country code to currency code, filled in below
my %country_to_currency;

# A map of currency symbol to currency code, filled in below
my %symbol_to_currency = ( );

# A map of all the known currency codes, filled in below
my %supported_currency_codes = ( );

# From the symbol used, guess the currency (this will override any location data that comes in with the user).
# This is pretty imprecise. Assume USD if a $ is used. More assumptions can be added here later to priorize
# currency guessing. Perhaps location data can be used to break ties.
sub convert_symbol_to_currency {
    my ($symbol, $country) = @_;
    my @syms = @{$symbol_to_currency{$symbol} // []};
    return "USD" unless @syms;
    return $syms[0] if @syms == 1 or !$country;

    my @matches = grep { $_ eq $country_to_currency{$country} } @syms;
    return $matches[0] if @matches;
    return $syms[0];
}

# Given the country code and currency formatting rules, the input can be made ready to convert
# to a useable number. Examples:
# In USD: 400,000 => 400000
# In EUR: 400.000,00 => 400000.00
sub normalize_formatted_currency_string {
    my ($str, $currency_code) = @_;

    my $thousands_separator = thousands_separator($currency_code) // ',';
    my $decimal_separator = decimal_separator($currency_code) // '.';

    $str =~ s/\Q$thousands_separator//g;
    $str =~ s/\Q$decimal_separator/\./g if ($decimal_separator ne ".");

    return $str;
}

# Attempt to extract terms of a loan from the input query. Interest rate and principal must be found in the
# query, downpayment and years of the loan are optional and will default to 0 and 30, respectively. This
# method first looks for years, interest rate, and downpayment numbers and removes them from the query
# string so they are not confused for the principal loan amount, which may be present simply as a raw
# number.
handle remainder => sub {
    my $query = $_;

    # Loan rate is required, query will be ignored if it is missing
    my $rate = 0;

    # Years in the query is optional, it will be assumed to be 30 if not present
    my $years = 30;

    # Check if query contains number of years for loan, if so, save value and remove it
    if ($query =~ /(\d+) ?(?:year|yr)s?/) {
        $years = $1;
        $query =~ s/(\d+) ?(?:year|yr)s?//;
    }

    # Try to extract the interest rate and remove it. Interest rate is required.
    return unless ($query =~ /(\d+\.?\d*)%/);

    $rate = $1;
    $query =~ s/(\d+\.?\d*)%//;

    # Downpayment information that may be discovered. It will be combined with the discovered currency.
    my $downpayment_in_query = 0;
    my $downpayment_is_in_cash = 0;
    my $downpayment_without_units = 0;

    # Check if query contains downpayment information
    if ($query =~ /(\p{Currency_Symbol})?(\d+)\s?([A-Za-z]{3})?(%)? down/) {
        $downpayment_in_query = 1;
        $downpayment_is_in_cash = ! (defined $4);
        $downpayment_without_units = $2;
        $query =~ s/(\p{Currency_Symbol})?(\d+)\s?([A-Za-z]{3})?(%)? down//;
    }

    # At a minimum, query should contain some amount of money
    if ($query =~ /(\p{Currency_Symbol})?([\d\.,]+)\s?([A-Za-z]{3})?/) {
        my $symbol = $1;
        my $principal = $2;
        my $input_currency_code = $3;
        my $downpayment = 0;

        $input_currency_code = uc($input_currency_code) if (defined $input_currency_code);

        # Apply localization, default to US if unknown
        my $currency_code = "USD";
        if (defined $input_currency_code && exists $supported_currency_codes{$input_currency_code}) {
            $currency_code = $input_currency_code;
        } elsif (defined $symbol) {
            $currency_code = convert_symbol_to_currency($symbol, lc $loc->country_code);
        } elsif (defined $loc->country_code) {
            $currency_code = $country_to_currency{lc $loc->country_code} || $country_to_currency{"us"};
            $symbol = currency_symbol($currency_code);
        }

        # Given the country code and currency formatting rules, the input can be made ready to convert
        # to a useable number.
        $principal = normalize_formatted_currency_string($principal, $currency_code);

        # Deal with downpayment information if it was found in the query
        if ($downpayment_in_query) {
            if ($downpayment_is_in_cash) {
                # Downpayment expresses in an amount of currency
                $downpayment = normalize_formatted_currency_string($downpayment_without_units, $currency_code);
            } else {
                # Downpayment expressed as a percentage of principal
                $downpayment = $principal * .01 * $downpayment_without_units;
            }
        }

        my $loan_amount = $principal - $downpayment;
        my $monthly_payment = loan_monthly_payment($loan_amount, $rate / 12 * .01, $years * 12);
        my $total_interest = ($monthly_payment * 12 * $years) - $loan_amount;

        my $title = currency_format($currency_code, $monthly_payment, FMT_SYMBOL) . " for $years years";
        my $subtitle = "Total interest paid is " . currency_format($currency_code, $total_interest, FMT_SYMBOL);
        my $string_answer = "Montly payment is " . $title . ". " . $subtitle;
        return $string_answer, structured_answer => {
            data => {
                title => $title,
                subtitle => $subtitle,
            },
            templates => {
                group => 'text'
            }
        }

    }
    return;
};

# A map of 2 letter country code to 3 letter currency code. Copied from Locale::Object perl module
# (http://search.cpan.org/~jrobinson/Locale-Object/) which carries some extra baggage with it, and
# has also not been updated since 2007. If the mapping between any country and currency needs to be
# changed, this is where to change it.
%country_to_currency = (
    'ad' => 'EUR',
    'ae' => 'AED',
    'af' => 'AFA',
    'ag' => 'XCD',
    'ai' => 'XCD',
    'al' => 'ALL',
    'am' => 'AMD',
    'an' => 'ANG',
    'ao' => 'AOA',
    'aq' => '000',
    'ar' => 'ARS',
    'as' => 'USD',
    'at' => 'EUR',
    'au' => 'AUD',
    'aw' => 'AWG',
    'az' => 'AZM',
    'ba' => 'BAM',
    'bb' => 'BBD',
    'bd' => 'BDT',
    'be' => 'EUR',
    'bf' => 'XOF',
    'bg' => 'BGL',
    'bh' => 'BHD',
    'bi' => 'BIF',
    'bj' => 'XOF',
    'bm' => 'BMD',
    'bn' => 'BND',
    'bo' => 'BOB',
    'br' => 'BRL',
    'bs' => 'BSD',
    'bt' => 'BTN',
    'bv' => 'NOK',
    'bw' => 'BWP',
    'by' => 'BYR',
    'bz' => 'BZD',
    'ca' => 'CAD',
    'cc' => 'AUD',
    'cd' => 'CDF',
    'cf' => 'XAF',
    'cg' => 'XAF',
    'ch' => 'CHF',
    'ci' => 'XOF',
    'ck' => 'NZD',
    'cl' => 'CLP',
    'cm' => 'XAF',
    'cn' => 'CNY',
    'co' => 'COP',
    'cr' => 'CRC',
    'cu' => 'CUP',
    'cv' => 'CVE',
    'cx' => 'AUD',
    'cy' => 'CYP',
    'cz' => 'CZK',
    'de' => 'EUR',
    'dj' => 'DJF',
    'dk' => 'DKK',
    'dm' => 'XCD',
    'do' => 'DOP',
    'dz' => 'DZD',
    'ec' => 'ECS',
    'ee' => 'EEK',
    'eg' => 'EGP',
    'eh' => 'MAD',
    'er' => 'ERN',
    'es' => 'EUR',
    'et' => 'ETB',
    'fi' => 'EUR',
    'fj' => 'FJD',
    'fk' => 'FKP',
    'fm' => 'USD',
    'fo' => 'DKK',
    'fr' => 'EUR',
    'fx' => 'EUR',
    'ga' => 'XAF',
    'gb' => 'GBP',
    'gd' => 'XCD',
    'ge' => 'GEL',
    'gf' => 'EUR',
    'gh' => 'GHC',
    'gi' => 'GIP',
    'gl' => 'DKK',
    'gm' => 'GMD',
    'gn' => 'GNF',
    'gp' => 'EUR',
    'gq' => 'GQE',
    'gr' => 'EUR',
    'gs' => 'GBP',
    'gt' => 'GTQ',
    'gu' => 'USD',
    'gw' => 'XOF',
    'gy' => 'GYD',
    'hk' => 'HKD',
    'hm' => 'AUD',
    'hn' => 'HNL',
    'hr' => 'HRK',
    'ht' => 'HTG',
    'hu' => 'HUF',
    'id' => 'IDR',
    'ie' => 'EUR',
    'il' => 'ILS',
    'in' => 'INR',
    'io' => 'GBP',
    'iq' => 'IQD',
    'ir' => 'IRR',
    'is' => 'ISK',
    'it' => 'EUR',
    'jm' => 'JMD',
    'jo' => 'JOD',
    'jp' => 'JPY',
    'ke' => 'KES',
    'kg' => 'KGS',
    'kh' => 'KHR',
    'ki' => 'AUD',
    'km' => 'KMF',
    'kn' => 'XCD',
    'kp' => 'KPW',
    'kr' => 'KRW',
    'kw' => 'KWD',
    'ky' => 'KYD',
    'kz' => 'KZT',
    'la' => 'LAK',
    'lb' => 'LBP',
    'lc' => 'XCD',
    'li' => 'CHF',
    'lk' => 'LKR',
    'lr' => 'LRD',
    'ls' => 'LSL',
    'lt' => 'LTL',
    'lu' => 'EUR',
    'lv' => 'LVL',
    'ly' => 'LYD',
    'ma' => 'MAD',
    'mc' => 'EUR',
    'md' => 'MDL',
    'me' => 'YUM',
    'mg' => 'MGF',
    'mh' => 'USD',
    'mk' => 'MKD',
    'ml' => 'XOF',
    'mm' => 'MMK',
    'mn' => 'MNT',
    'mo' => 'MOP',
    'mp' => 'USD',
    'mq' => 'EUR',
    'mr' => 'MRO',
    'ms' => 'XCD',
    'mt' => 'MTL',
    'mu' => 'MUR',
    'mv' => 'MVR',
    'mw' => 'MWK',
    'mx' => 'MXN',
    'my' => 'MYR',
    'mz' => 'MZM',
    'na' => 'NAD',
    'nc' => 'XPF',
    'ne' => 'XOF',
    'nf' => 'AUD',
    'ng' => 'NGN',
    'ni' => 'NIO',
    'nl' => 'EUR',
    'no' => 'NOK',
    'np' => 'NPR',
    'nr' => 'AUD',
    'nu' => 'NZD',
    'nz' => 'NZD',
    'om' => 'OMR',
    'pa' => 'PAB',
    'pe' => 'PEN',
    'pf' => 'XPF',
    'pg' => 'PGK',
    'ph' => 'PHP',
    'pk' => 'PKR',
    'pl' => 'PLN',
    'pm' => 'EUR',
    'pn' => 'NZD',
    'pr' => 'USD',
    'ps' => 'ILS',
    'pt' => 'EUR',
    'pw' => 'USD',
    'py' => 'PYG',
    'qa' => 'QAR',
    're' => 'EUR',
    'ro' => 'ROL',
    'rs' => 'YUM',
    'ru' => 'RUB',
    'rw' => 'RWF',
    'sa' => 'SAR',
    'sb' => 'SBD',
    'sc' => 'SCR',
    'sd' => 'SDP',
    'se' => 'SEK',
    'sg' => 'SGD',
    'sh' => 'SHP',
    'si' => 'SIT',
    'sj' => 'NOK',
    'sk' => 'SKK',
    'sl' => 'SLL',
    'sm' => 'EUR',
    'sn' => 'XOF',
    'so' => 'SOS',
    'sr' => 'SRG',
    'st' => 'STD',
    'sv' => 'SVC',
    'sy' => 'SYP',
    'sz' => 'SZL',
    'tc' => 'USD',
    'td' => 'XAF',
    'tf' => 'EUR',
    'tg' => 'XOF',
    'th' => 'THB',
    'tj' => 'TJS',
    'tk' => 'NZD',
    'tl' => 'IDR',
    'tm' => 'TMM',
    'tn' => 'TND',
    'to' => 'TOP',
    'tr' => 'TRL',
    'tt' => 'TTD',
    'tv' => 'AUD',
    'tw' => 'TWD',
    'tz' => 'TZS',
    'ua' => 'UAH',
    'ug' => 'UGX',
    'um' => 'USD',
    'us' => 'USD',
    'uy' => 'UYU',
    'uz' => 'UZS',
    'va' => 'EUR',
    'vc' => 'XCD',
    've' => 'VEB',
    'vg' => 'USD',
    'vi' => 'USD',
    'vn' => 'VND',
    'vu' => 'VUV',
    'wf' => 'XPF',
    'ws' => 'WST',
    'ye' => 'YER',
    'yt' => 'EUR',
    'yu' => 'YUM',
    'za' => 'ZAR',
    'zm' => 'ZMK',
    'zr' => 'XAF',
    'zw' => 'ZWD');

%symbol_to_currency = (
    "\x{20a1}" => [
        "CRC",
    ],
    "Ls" => [
        "LVL",
    ],
    "TT\$" => [
        "TTD",
    ],
    "\x{20ae}" => [
        "MNT",
    ],
    "\x{043b}\x{0432}" => [
        "BGN",
        "KZT",
        "KGS",
        "UZS",
    ],
    "Z\$" => [
        "ZWD",
    ],
    "\x{0e3f}" => [
        "THB",
    ],
    "CHF" => [
        "CHF",
    ],
    "\x{0434}\x{0435}\x{043d}" => [
        "MKD",
    ],
    "p." => [
        "BYR",
    ],
    "\x{20a9}" => [
        "KPW",
        "KRW",
        "KPW",
        "KRW",
    ],
    "S/." => [
        "PEN",
    ],
    "NT\$" => [
        "TWD",
    ],
    "\x{0414}\x{0438}\x{043d}." => [
        "RSD",
    ],
    "Bs" => [
        "VEF",
    ],
    "K\x{010d}" => [
        "CZK",
    ],
    "\x{fdfc}" => [
        "IRR",
        "OMR",
        "QAR",
        "SAR",
        "YER",
    ],
    "J\$" => [
        "JMD",
    ],
    "\x{00a2}" => [
        "GHC",
    ],
    "\x{17db}" => [
        "KHR",
    ],
    "lei" => [
        "RON",
    ],
    "R" => [
        "ZAR",
    ],
    "\x{043c}\x{0430}\x{043d}" => [
        "AZN",
    ],
    "\x{20b1}" => [
        "CUP",
        "PHP",
    ],
    "Gs" => [
        "PYG",
    ],
    "\$" => [
        "USD",
        "ARS",
        "AUD",
        "BSD",
        "BBD",
        "BMD",
        "BND",
        "CAD",
        "KYD",
        "CLP",
        "COP",
        "XCD",
        "SVC",
        "FJD",
        "GYD",
        "HKD",
        "LRD",
        "MXN",
        "NAD",
        "NZD",
        "SGD",
        "SBD",
        "SRD",
        "TVD",
    ],
    "\x{20a6}" => [
        "NGN",
    ],
    "kn" => [
        "HRK",
    ],
    "\x{0192}" => [
        "AWG",
        "ANG",
    ],
    "kr" => [
        "DKK",
        "EEK",
        "ISK",
        "NOK",
        "SEK",
    ],
    "S" => [
        "SOS",
    ],
    "\x{20ab}" => [
        "VND",
    ],
    "Lek" => [
        "ALL",
    ],
    "P" => [
        "BWP",
    ],
    "BZ\$" => [
        "BZD",
    ],
    "R\$" => [
        "BRL",
    ],
    "\x{20aa}" => [
        "ILS",
    ],
    "MT" => [
        "MZN",
    ],
    "Rp" => [
        "IDR",
    ],
    "\$b" => [
        "BOB",
    ],
    "Lt" => [
        "LTL",
    ],
    "Ft" => [
        "HUF",
    ],
    "C\$" => [
        "NIO",
    ],
    "\x{00a3}" => [
        "GBP",
        "EGP",
        "FKP",
        "GIP",
        "GGP",
        "IMP",
        "JEP",
        "LBP",
        "SHP",
        "SYP",
    ],
    "\x{20a8}" => [
        "MUR",
        "NPR",
        "PKR",
        "SCR",
        "LKR",
    ],
    "\x{00a5}" => [
        "CNY",
        "JPY",
    ],
    "\x{20ad}" => [
        "LAK",
    ],
    "z\x{0142}" => [
        "PLN",
    ],
    "\x{20b4}" => [
        "UAH",
    ],
    "B/." => [
        "PAB",
    ],
    "\x{060b}" => [
        "AFN",
    ],
    "\x{20ac}" => [
        "EUR",
    ],
    "L" => [
        "HNL",
    ],
    "RD\$" => [
        "DOP",
    ],
    "KM" => [
        "BAM",
    ],
    "\$U" => [
        "UYU",
    ],
    "\x{20a4}" => [
        "TRL",
    ],
    "Q" => [
        "GTQ",
    ],
    "RM" => [
        "MYR",
    ],
    "\x{0440}\x{0443}\x{0431}" => [
        "RUB",
    ],
);

foreach my $code (values %country_to_currency) {
    $supported_currency_codes{$code} = 1;
}

1;
